using System;
using System.Collections.Generic;
using MyReactivity;
using MyReactivity.Abstractions;
using Project.Models;
using Project.Models.Enums;
using Project.ViewModels.Abstractions;

namespace Project.ViewModels
{
    public class InventoryViewModel : ViewModelBase<Inventory>, IDisposable
    {
        private readonly Random _random;
        private readonly ItemConfigsStorage _itemConfigsStorage;
        private readonly ReactiveList<SlotViewModel> _slotViewModels;
        private readonly ReactiveProperty<bool> _canAddSlotToEnd;
        private readonly ReactiveProperty<bool> _canRemoveSlotFromEnd;
        private readonly Dictionary<int, Action<bool>> _itemIsDraggingValueChangedMap;
        private readonly Dictionary<int, Action> _itemDroppedMap;

        private Slot _startingSlot;

        public InventoryViewModel(Inventory inventory, ItemConfigsStorage itemConfigsStorage) : base(inventory)
        {
            if (itemConfigsStorage == null)
                throw new NullReferenceException(nameof(itemConfigsStorage));

            _random = new Random();
            _itemConfigsStorage = itemConfigsStorage;
            _slotViewModels = new ReactiveList<SlotViewModel>();
            _canAddSlotToEnd = new ReactiveProperty<bool>();
            _canRemoveSlotFromEnd = new ReactiveProperty<bool>();
            _itemIsDraggingValueChangedMap = new Dictionary<int, Action<bool>>();
            _itemDroppedMap = new Dictionary<int, Action>();

            for (int i = 0, length = Model.Slots.Count; i < length; i++)
                OnSlotsElementAdded(i, Model.Slots[i]);

            RefreshCanAddSlotToEndAndCanRemoveSlotFromEnd();

            Model.Slots.ElementAdded += OnSlotsElementAdded;
            Model.Slots.ElementRemoved += OnSlotsElementRemoved;
        }

        public IReadOnlyReactiveList<SlotViewModel> SlotViewModels => _slotViewModels;
        public IReadOnlyReactiveProperty<bool> CanAddSlotToEnd => _canAddSlotToEnd;
        public IReadOnlyReactiveProperty<bool> CanRemoveSlotFromEnd => _canRemoveSlotFromEnd;
        public IReadOnlyReactiveProperty<bool> IsEmpty => Model.IsEmpty;

        public void Dispose()
        {
            Model.Slots.ElementAdded -= OnSlotsElementAdded;
            Model.Slots.ElementRemoved -= OnSlotsElementRemoved;
        }

        public void Reset() => 
            Model.Reset();

        public void OrderItemsByItemId() => 
            Model.OrderItems(OrderItemsInInventoryBy.ItemId);
        public void OrderItemsByQuantityOfItems() => 
            Model.OrderItems(OrderItemsInInventoryBy.QuantityOfItems);
        public void OrderItemsByItemName() => 
            Model.OrderItems(OrderItemsInInventoryBy.ItemName);

        public void GroupItems() => 
            Model.GroupItems();

        public void AddRandomItemsToRandomSlot()
        {
            //var randomSlotIndex = _random.Next(0, Model.Slots.Count);
            //var randomItemIdIndex = _random.Next(0, _itemConfigsStorage.ItemIds.Count);
            //var randomSlot = Model.Slots[randomSlotIndex];
            //var randomItemId = _itemConfigsStorage.ItemIds[randomItemIdIndex];
            //var remainingQuantityOfItems = _random.Next(1, _itemConfigsStorage[randomItemId].MaxQuantity + 1);

            //if (randomSlot.IsEmpty.Value || randomSlot.ItemId.Value == randomItemId)
            //    remainingQuantityOfItems = randomSlot.Add(randomItemId, remainingQuantityOfItems);

            //Model.AddItems(randomItemId, remainingQuantityOfItems);

            var randomItemId = _itemConfigsStorage.ItemIds[_random.Next(0, _itemConfigsStorage.ItemIds.Count)];

            Model.AddItems(
                randomItemId,
                _random.Next(1, _itemConfigsStorage[randomItemId].MaxQuantity + 1));
        }

        public void AddSlotToEnd() => 
            Model.AddSlotToEnd();
        public void RemoveSlotFromEnd() => 
            Model.RemoveSlotFromEnd();

        private void OnSlotsElementAdded(int index, Slot addedSlot)
        {
            var slotViewModel = new SlotViewModel(addedSlot, _itemConfigsStorage);

            _slotViewModels.Insert(index, slotViewModel);

            _itemIsDraggingValueChangedMap.Add(index, (value) => OnIsDraggingValueChanged(addedSlot, value));
            _itemDroppedMap.Add(index, () => OnItemDropped(addedSlot));

            slotViewModel.ItemViewModel.IsDragging.ValueChanged += _itemIsDraggingValueChangedMap[index];
            slotViewModel.ItemDropped += _itemDroppedMap[index];

            RefreshCanAddSlotToEndAndCanRemoveSlotFromEnd();
        }
        private void OnSlotsElementRemoved(int index, Slot removedSlot)
        {
            var slotViewModel = _slotViewModels[index];

            slotViewModel.ItemViewModel.IsDragging.ValueChanged -= _itemIsDraggingValueChangedMap[index];
            slotViewModel.ItemDropped -= _itemDroppedMap[index];

            _itemIsDraggingValueChangedMap.Remove(index);
            _itemDroppedMap.Remove(index);

            _slotViewModels.RemoveAt(index);

            RefreshCanAddSlotToEndAndCanRemoveSlotFromEnd();
        }

        private void OnIsDraggingValueChanged(Slot slot, bool newValue)
        {
            if (newValue)
                _startingSlot = slot;
        }
        private void OnItemDropped(Slot slot)
        {
            if (_startingSlot == slot)
                return;

            if (slot.IsEmpty.Value)
            {
                slot.Set(_startingSlot);
                _startingSlot.Clear();
            }
            else if (_startingSlot.ItemId.Value == slot.ItemId.Value)
            {
                var quantityOfItems = _startingSlot.QuantityOfItems.Value;
                var remainingQuantityOfItems = slot.Add(_startingSlot.QuantityOfItems.Value);

                _startingSlot.Remove(quantityOfItems - remainingQuantityOfItems);
            }
            else
            {
                var tempSlot = slot.Copy();

                slot.Set(_startingSlot);
                _startingSlot.Set(tempSlot);
            }
        }
    
        private void RefreshCanAddSlotToEndAndCanRemoveSlotFromEnd()
        {
            _canAddSlotToEnd.Value = Model.Slots.Count < Model.MaxQuantityOfSlots;
            _canRemoveSlotFromEnd.Value = Model.Slots.Count > Model.MinQuantityOfSlots;
        }
    }
}
